﻿namespace PugLib.Extensions
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics.CodeAnalysis;
	using System.Threading;
	using System.Threading.Tasks;

	/// <summary>
	/// Threading related extensions methods
	/// </summary>
	public static class ThreadingExtensions
	{
		/// <summary>
		/// Wrapper around Task.Factory.StartNew(Action)
		/// </summary>
		/// <param name="action"></param>
		public static Task StartAction(this Action action)
		{
			return Task.Factory.StartNew(action);
		}

		/// <summary>
		/// Typed wrapper around Task.Factory.StartNew(Action, Object)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="action"></param>
		/// <param name="state"></param>
		public static Task StartAction<T>(this Action<T> action, T state)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}

			return Task.Factory.StartNew(s => action((T)s), state);
		}

		/// <summary>
		/// Starts the action with the arguments provided in a background task.
		/// </summary>
		/// <typeparam name="TInput1"></typeparam>
		/// <typeparam name="TInput2"></typeparam>
		/// <param name="action"></param>
		/// <param name="input1"></param>
		/// <param name="input2"></param>
		/// <returns></returns>
		public static Task StartAction<TInput1, TInput2>(this Action<TInput1, TInput2> action, TInput1 input1, TInput2 input2)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}

			return Task.Factory.StartNew(() => action(input1, input2));
		}

		/// <summary>
		/// Starts the action with the arguments provided in a background task.
		/// </summary>
		/// <param name="action"></param>
		/// <param name="taskOptions"></param>
		/// <param name="continuation"></param>
		/// <param name="uiScheduler"></param>
		public static void StartAction(Action action, TaskCreationOptions taskOptions, Action<Task> continuation, TaskScheduler uiScheduler)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			if (continuation == null)
			{
				throw new ArgumentNullException("continuation");
			}
			if (uiScheduler == null)
			{
				throw new ArgumentNullException("uiScheduler");
			}

			Task.Factory.StartNew(action, taskOptions).ContinueWith(continuation, uiScheduler);
		}

		/// <summary>
		/// Typed wrapper around Task.Factory.StartNew
		/// </summary>
		/// <typeparam name="TInput"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="func"></param>
		/// <param name="state"></param>
		/// <returns></returns>
		public static Task<TResult> StartFunc<TInput, TResult>(this Func<TInput, TResult> func, TInput state)
		{
			if (func == null)
			{
				throw new ArgumentNullException("func");
			}

			return Task.Factory.StartNew(s => func(state), state);
		}

		/// <summary>
		/// Typed wrapper around Task.Factory.StartNew
		/// </summary>
		/// <typeparam name="TInput"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="func"></param>
		/// <param name="state"></param>
		/// <param name="cancellationToken"></param>
		/// <returns></returns>
		public static Task<TResult> StartFunc<TInput, TResult>(this Func<TInput, TResult> func, TInput state, CancellationToken cancellationToken)
		{
			if (func == null)
			{
				throw new ArgumentNullException("func");
			}

			return Task.Factory.StartNew(s => func(state), state, cancellationToken);
		}


		/// <summary>
		/// Wrapper around Task.Factory.StartNew(Action)
		/// </summary>
		/// <param name="action"></param>
		public static Task<TResult> StartFunc<TResult>(this Func<TResult> action)
		{
			return Task.Factory.StartNew(action);
		}

		/// <summary> 
		/// Creates a <see cref="System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of
		/// begin and end methods that conform to the Asynchronous Programming Model pattern.
		/// </summary>
		/// <typeparam name="TResult">The type of the result available through the 
		/// <see cref="System.Threading.Tasks.Task{TResult}">Task</see>.
		/// </typeparam> 
		/// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> 
		/// <param name="endMethod">The delegate that ends the asynchronous operation.</param>
		/// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the 
		/// <paramref name="beginMethod"/> argument is null.</exception>
		/// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the 
		/// <paramref name="endMethod"/> argument is null.</exception> 
		/// <returns>The created <see cref="System.Threading.Tasks.Task{TResult}">Task</see> that
		/// represents the asynchronous operation.</returns>
		/// <remarks> 
		/// This method throws any exceptions thrown by the <paramref name="beginMethod"/>.
		/// </remarks> 
		public static Task<TResult> FromAsync<TResult>(Func<AsyncCallback, Object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod)
		{
			return Task.Factory.FromAsync(beginMethod, endMethod, null, TaskCreationOptions.None);
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		public static void StartTaskWithUIResult<TResult>(this Func<TResult> taskFunction,
														  Func<Exception, Boolean> exceptionHandler,
														  Action<TResult> updateUIAction,
														  TaskScheduler uiScheduler)
		{
			StartTaskWithUIResult(taskFunction, exceptionHandler, updateUIAction, uiScheduler, CancellationToken.None, TaskCreationOptions.None);
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <param name="taskFunction"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		public static void StartTaskWithUIResult(this Action taskFunction,
												 Func<Exception, Boolean> exceptionHandler,
												 Action updateUIAction,
												 TaskScheduler uiScheduler)
		{
			StartTaskWithUIResult(taskFunction, exceptionHandler, updateUIAction, uiScheduler, CancellationToken.None, TaskCreationOptions.None);
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		/// <param name="token"></param>
		/// <remarks>The Task representing <paramref name="taskFunction"/></remarks>
		public static Task<TResult> StartTaskWithUIResult<TResult>(this Func<TResult> taskFunction,
																   Func<Exception, Boolean> exceptionHandler,
																   Action<TResult> updateUIAction,
																   TaskScheduler uiScheduler, CancellationToken token)
		{
			return StartTaskWithUIResult(taskFunction, exceptionHandler, updateUIAction, uiScheduler, token, TaskCreationOptions.None);
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="beginTask"></param>
		/// <param name="endTask"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		/// <returns></returns>
		public static Task<TResult> StartTaskWithUIResult<TResult>(this Func<AsyncCallback, Object, IAsyncResult> beginTask,
																   Func<IAsyncResult, TResult> endTask,
																   Func<Exception, Boolean> exceptionHandler,
																   Action<TResult> updateUIAction,
																   TaskScheduler uiScheduler)
		{
			Task<TResult> task = ThreadingExtensions.FromAsync(beginTask, endTask);
			task.ContinueWith(state =>
			{
				try
				{
					updateUIAction(state.Result);
				}
				catch (AggregateException ex)
				{
					ex.Flatten().Handle(exceptionHandler);
				}
			}, uiScheduler);
			return task;
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <typeparam name="TArg">argument type to beginTask</typeparam>
		/// <param name="beginTask"></param>
		/// <param name="endTask"></param>
		/// <param name="arg1"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		/// <param name="taskOptions"></param>
		/// <returns></returns>
		public static Task<TResult> StartTaskWithUIResult<TArg, TResult>(this Func<TArg, AsyncCallback, Object, IAsyncResult> beginTask,
																		 Func<IAsyncResult, TResult> endTask,
																		 TArg arg1,
																		 Func<Exception, Boolean> exceptionHandler,
																		 Action<TResult> updateUIAction,
																		 TaskScheduler uiScheduler, TaskCreationOptions taskOptions)
		{
			Task<TResult> task = Task.Factory.FromAsync(beginTask, endTask, arg1, null, taskOptions);
			task.ContinueWith(state =>
			{
				try
				{
					updateUIAction(state.Result);
				}
				catch (AggregateException ex)
				{
					ex.Flatten().Handle(exceptionHandler);
				}
			}, uiScheduler);
			return task;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="task"></param>
		/// <param name="handleTaskExceptions"></param>
		/// <param name="updateUI"></param>
		/// <param name="uiScheduler"></param>
		public static Task ContinueTaskWithUIResult<TResult>(this Task<TResult> task, Func<Exception, Boolean> handleTaskExceptions, Action<TResult> updateUI, TaskScheduler uiScheduler)
		{
			if (task == null)
			{
				throw new ArgumentNullException("task");
			}
			if (handleTaskExceptions == null)
			{
				throw new ArgumentNullException("handleTaskExceptions");
			}
			if (updateUI == null)
			{
				throw new ArgumentNullException("updateUI");
			}
			if (uiScheduler == null)
			{
				throw new ArgumentNullException("uiScheduler");
			}
			return task.ContinueWith(state =>
			{
				try
				{
					updateUI(state.Result);
				}
				catch (AggregateException ex)
				{
					ex.Flatten().Handle(handleTaskExceptions);
				}
			}, uiScheduler);
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the continuation on the provided scheduler.
		/// </summary>
		/// <param name="taskFunction"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		/// <param name="creationOptions"></param>
		/// <param name="token"></param>
		public static Task StartTaskWithUIResult(this Action taskFunction,
												 Func<Exception, Boolean> exceptionHandler,
												 Action updateUIAction,
												 TaskScheduler uiScheduler,
												 CancellationToken token, TaskCreationOptions creationOptions)
		{
			if (taskFunction == null)
			{
				throw new ArgumentNullException("taskFunction");
			}
			if (exceptionHandler == null)
			{
				throw new ArgumentNullException("exceptionHandler");
			}
			if (updateUIAction == null)
			{
				throw new ArgumentNullException("updateUIAction");
			}
			if (uiScheduler == null)
			{
				throw new ArgumentNullException("uiScheduler");
			}

			Task task = Task.Factory.StartNew(taskFunction, token, creationOptions, TaskScheduler.Default);
			task.ContinueWith(state =>
			{
				try
				{
					updateUIAction();
				}
				catch (AggregateException ex)
				{
					ex.Flatten().Handle(exceptionHandler);
				}
			}, uiScheduler);
			return task;
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		/// <param name="creationOptions"></param>
		/// <param name="token"></param>
		/// <remarks>The Task representing <paramref name="taskFunction"/></remarks>
		public static Task<TResult> StartTaskWithUIResult<TResult>(this Func<TResult> taskFunction,
																   Func<Exception, Boolean> exceptionHandler,
																   Action<TResult> updateUIAction,
																   TaskScheduler uiScheduler,
																   CancellationToken token, TaskCreationOptions creationOptions)
		{
			if (taskFunction == null)
			{
				throw new ArgumentNullException("taskFunction");
			}
			if (exceptionHandler == null)
			{
				throw new ArgumentNullException("exceptionHandler");
			}
			if (updateUIAction == null)
			{
				throw new ArgumentNullException("updateUIAction");
			}
			if (uiScheduler == null)
			{
				throw new ArgumentNullException("uiScheduler");
			}

			Task<TResult> originalTask = Task.Factory.StartNew(taskFunction, token, creationOptions, TaskScheduler.Default);
			originalTask.ContinueWith(state =>
			{
				try
				{
					updateUIAction(state.Result);
				}
				catch (AggregateException ex)
				{
					ex.Flatten().Handle(exceptionHandler);
				}
			}, token, TaskContinuationOptions.NotOnCanceled, uiScheduler);
			return originalTask;
		}

		/// <summary>
		/// Starts a background task and provides success and failure continuations.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="onCompletion"></param>
		/// <param name="onError"></param>
		/// <param name="uiScheduler"></param>
		/// <returns></returns>
		[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Favoring stronger type that is already generic.")]
		public static Task<TResult> StartTaskWithUIResult<TResult>(this Func<TResult> taskFunction,
																   Func<Exception, Boolean> exceptionHandler,
																   Action<TResult> onCompletion,
																   Action<Task<TResult>> onError,
																   TaskScheduler uiScheduler)
		{
			return StartTaskWithUIResult(taskFunction, exceptionHandler, onCompletion, onError, uiScheduler, CancellationToken.None, TaskCreationOptions.None);
		}

		/// <summary>
		/// Starts a background task and provides success and failure continuations.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="exceptionHandler"></param>
		/// <param name="onCompletion"></param>
		/// <param name="onError"></param>
		/// <param name="uiScheduler"></param>
		/// <param name="token"></param>
		/// <param name="creationOptions"></param>
		/// <returns></returns>
		[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Favoring stronger type that is already generic.")]
		public static Task<TResult> StartTaskWithUIResult<TResult>(this Func<TResult> taskFunction,
																   Func<Exception, Boolean> exceptionHandler,
																   Action<TResult> onCompletion,
																   Action<Task<TResult>> onError,
																   TaskScheduler uiScheduler,
																   CancellationToken token,
																   TaskCreationOptions creationOptions)
		{
			Task<TResult> originalTask = Task.Factory.StartNew(taskFunction, token, creationOptions, TaskScheduler.Default);
			originalTask.ContinueWith(onError, token, TaskContinuationOptions.OnlyOnFaulted, uiScheduler);
			originalTask.ContinueWith(state =>
			{
				try
				{
					onCompletion(state.Result);
				}
				catch (AggregateException ex)
				{
					ex.Flatten().Handle(exceptionHandler);
				}
			}, token, TaskContinuationOptions.OnlyOnRanToCompletion, uiScheduler);
			return originalTask;
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <typeparam name="TArg1"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="arg1"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		/// <param name="creationOptions"></param>
		public static void StartTaskWithUIResult<TArg1, TResult>(this Func<TArg1, TResult> taskFunction, TArg1 arg1,
																 Action<TResult> updateUIAction,
																 TaskScheduler uiScheduler,
																 TaskCreationOptions creationOptions)
		{
			if (taskFunction == null)
			{
				throw new ArgumentNullException("taskFunction");
			}
			if (updateUIAction == null)
			{
				throw new ArgumentNullException("updateUIAction");
			}
			if (uiScheduler == null)
			{
				throw new ArgumentNullException("uiScheduler");
			}

			Task.Factory.StartNew(() => taskFunction(arg1), creationOptions).ContinueWith(state => updateUIAction(state.Result), uiScheduler);
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <typeparam name="TArg1"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="arg1"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		public static void StartTaskWithUIResult<TArg1, TResult>(this Func<TArg1, TResult> taskFunction, TArg1 arg1,
																 Action<TResult> updateUIAction,
																 TaskScheduler uiScheduler)
		{
			StartTaskWithUIResult(taskFunction, arg1, updateUIAction, uiScheduler, TaskCreationOptions.None);
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <typeparam name="TArg1"></typeparam>
		/// <typeparam name="TState"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="arg1"></param>
		/// <param name="originalState"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		public static void StartTaskWithUIResult<TArg1, TResult, TState>(this Func<TArg1, TResult> taskFunction, TArg1 arg1,
																		 TState originalState,
																		 Action<TResult, TState> updateUIAction,
																		 TaskScheduler uiScheduler)
		{
			StartTaskWithUIResult(taskFunction, arg1, originalState, updateUIAction, uiScheduler, TaskCreationOptions.None);
		}

		/// <summary>
		/// Starts a task in a worker thread and runs the result action on the provided scheduler.
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <typeparam name="TArg1"></typeparam>
		/// <typeparam name="TState"></typeparam>
		/// <param name="taskFunction"></param>
		/// <param name="arg1"></param>
		/// <param name="originalState"></param>
		/// <param name="updateUIAction"></param>
		/// <param name="uiScheduler"></param>
		/// <param name="creationOptions"></param>
		public static void StartTaskWithUIResult<TArg1, TResult, TState>(this Func<TArg1, TResult> taskFunction, TArg1 arg1,
																		 TState originalState,
																		 Action<TResult, TState> updateUIAction,
																		 TaskScheduler uiScheduler,
																		 TaskCreationOptions creationOptions)
		{
			if (taskFunction == null)
			{
				throw new ArgumentNullException("taskFunction");
			}
			if (updateUIAction == null)
			{
				throw new ArgumentNullException("updateUIAction");
			}
			if (uiScheduler == null)
			{
				throw new ArgumentNullException("uiScheduler");
			}

			Task.Factory.StartNew(() => taskFunction(arg1), creationOptions).ContinueWith(state => updateUIAction(state.Result, originalState), uiScheduler);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="delay"></param>
		/// <param name="function"></param>
		/// <returns></returns>
		public static Task<T> RunDelayed<T>(TimeSpan delay, Func<T> function)
		{
			if (function == null)
			{
				throw new ArgumentNullException("function");
			}

			if (delay.TotalMilliseconds < 0)
			{
				throw new ArgumentOutOfRangeException("delay");
			}

			// performance cheat
			if (delay == TimeSpan.Zero)
			{
				return Task.Factory.StartNew(function);
			}

			return Task.Factory.StartNew(() =>
			{
				SpinWait.SpinUntil(() => false, delay);
				return function();
			}, TaskCreationOptions.LongRunning);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="delay"></param>
		/// <param name="action"></param>
		/// <returns></returns>
		public static Task RunDelayed(TimeSpan delay, Action action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}

			if (delay.TotalMilliseconds < 0)
			{
				throw new ArgumentOutOfRangeException("delay");
			}

			// performance cheat
			if (delay == TimeSpan.Zero)
			{
				return Task.Factory.StartNew(action);
			}

			return Task.Factory.StartNew(() =>
			{
				SpinWait.SpinUntil(() => false, delay);
				action();
			}, TaskCreationOptions.LongRunning);
		}

		/// <summary>
		/// Handles only <see cref="TaskCanceledException"/> and <see cref="OperationCanceledException"/>
		/// </summary>
		/// <param name="ex"></param>
		/// <returns></returns>
		public static Boolean HandleCancelExceptions(Exception ex)
		{
			return ex is TaskCanceledException || ex is OperationCanceledException;
		}

		/// <summary>
		/// Calls wait and handles cancelation exceptions and disposes after the wait.
		/// </summary>
		/// <param name="task"></param>
		public static void WaitForAndDispose(this Task task)
		{
			if (task == null)
			{
				throw new ArgumentNullException("task");
			}
			try
			{
				task.Wait();
			}
			catch (AggregateException ex)
			{
				ex.Flatten().Handle(ThreadingExtensions.HandleCancelExceptions);
			}
			finally
			{
				task.Dispose();
			}
		}

		public static IList<TResult> ParallelMap<TArg, TResult>(this IList<TArg> list, Func<TArg, TResult> action)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}

			TResult[] result = new TResult[list.Count];
			Parallel.For(0, result.Length, i => { result[i] = action(list[i]); });
			return result;
		}

		/// <summary>
		/// Wraps an AsyncCallback with a synchronization context post message.
		/// </summary>
		/// <param name="callback"></param>
		/// <returns></returns>
		public static AsyncCallback ToSyncContextCallback(this AsyncCallback callback)
		{
			if (callback == null)
			{
				throw new ArgumentNullException("callback");
			}

			// Capture the calling thread's SynchronizationContext-derived object
			SynchronizationContext sc = SynchronizationContext.Current;

			// If there is no SC, just return what was passed in
			if (sc == null)
			{
				return callback;
			}

			// Return a delegate that, when invoked, posts to the captured SC a method that
			// calls the original AsyncCallback passing it the IAsyncResult argument
			return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
		}
	}
}